package gui;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;

import game.*;
import game.buildings.*;
import game.resources.*;
import game.saints.Saint;
import gui.border.*;
import main.*;

/**
 *	@(#)GameUtilities.java
 *
 *	The <code>GameUtilities</code> contains a series of useful creation
 *	methods used by the game view to create its components.
 *
 *	@author Grzegorz Kobiela
 *	@version 1.03 2008/6/8
 */
public abstract class GameUtilities
{
	/**
	 *	Returns the icon for the given object.
	 *	Provide the file name of the image to use for the icon.
	 *	@param object the object
	 *	@param file the file name
	 *	@return the icon
	 */
	public static ImageIcon getIcon(Object object, String file) {
		return new ImageIcon(GameUtilities.class.getResource(Antiquity.getDirectory(object) + file.toLowerCase() + ".png"));
	}

	/**
	 *	Returns the icon for the given object.
	 *	Provide arguments if necessary to specifiy the image to use.
	 *	@param o the object
	 *	@param args specifying arguments
	 *	@return the icon
	 */
	public static ImageIcon createIcon(Object object, Object... args) {
		String type = object.toString();

		/* Colored objects */
		if(object instanceof Colored || object instanceof PlayerColor) {
			if(object instanceof PlayerColor) object = new Marker((PlayerColor)object);
			return getIcon(object, ((Colored)object).getColor().toString());
		}

		/* Building */
		if(object instanceof Building) {
			/* Graves */
			if(object instanceof Grave) {
				return getIcon(object, type + ((Grave)object).getIndex());
			}

			/* Houses */
			if(object instanceof House) {	// args[0]: true if front of house has to be visible
				int i = ((House)object).getIndex();
				if(args.length > 0 && (Boolean)args[0]) {
					i = (i - 1) % 10;
					type += "_" + (i < 4 ? "a" : (i < 7 ? "b" : "c"));
				} else {
					type += i;
				}
				return getIcon(object, type);
			}

			/* Storages */
			if(object instanceof Storage) {
				ImageIcon icon = getIcon(object, "storage");
				Storage storage = (Storage)object;

				/* Return quickly if storage has standard size */
				if(storage.getCapacity() == 2) return icon;

				/* Create storage image */
				Image tile = icon.getImage();
				int dx = tile.getWidth(null);
				int dy = tile.getHeight(null);
				Dimension size = storage.getPolygon().getBounds().getSize();
				size.width /= 2;
				BufferedImage img = new BufferedImage(size.width * dx, size.height * dy, BufferedImage.TYPE_INT_ARGB);
				Graphics g = img.createGraphics();
				for(int x = 0; x < size.width; x++) for(int y = 0; y < size.height; y++) g.drawImage(tile, x * dx, y * dy, null);
				return new ImageIcon(img);
			}

			/* Fishermen */
			if(object instanceof Fisherman) {
				return getIcon(object, type + ((Rotatable)object).getRotation());
			}
		}

		/* Citymap */
		if(object instanceof Citymap) {
			return getIcon(object, type + (((Citymap)object).getSize() < 7 ? 2 : 1));
		}

		/* Exploration chit */
		if(object instanceof ExplorationChit) {
			return getIcon(object, (args.length > 0 ? args[0].toString() : "hidden"));
		}

		/* Famine track */
		if(object instanceof Famine) {
			return getIcon(object, type + args[0]);
		}

		/* Landtile */
		if(object instanceof Landtile) {
			Landtile tile = (Landtile)object;
			return ImageRotator.rotate(getIcon(object, type + tile.getType()), tile.getAngle());
		}

		/* Other game objects */
		return getIcon(object, type);
	}

	/**
	 *	Returns a label with a highlight image for the given object.
	 *	@param object the object
	 *	@return the label
	 */
	public static GameLabel createHighlight(Object object) {
		Polygon outline = getOutline(object);
		Point location = null;

		/* Get the hexfield location */
		if(object instanceof Hexfield) {
			location = ((Hexfield)object).getLocation();

		/* Get the landtile location */
		} else if(object instanceof Landtile) {
			location = ((Landtile)object).getLocation();

		/* Unsupported object */
		} else {
			throw new IllegalArgumentException(object + " is not supported by GameUtilities.createHighlight(Object)!");
		}

		/* Draw the highlight image */
		location = LandSystem.getPixel(location);
		outline.translate(location.x, location.y);
		Rectangle bounds = outline.getBounds();
		BufferedImage img = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = img.createGraphics();
		g.setColor(Antiquity.getColor("Highlight"));
		g.translate(-bounds.x, -bounds.y);
		g.fillPolygon(outline);
		g.translate(bounds.x, bounds.y);

		/* Create the label */
		GameLabel label = new GameLabel(new ImageIcon(img), Antiquity.getAlpha("Highlight"));
		if(object instanceof Landtile) bounds.translate(-bounds.width / 2, -bounds.height / 2);
		label.setBounds(bounds);
		return label;
	}

	/**
	 *	Returns the game label for the given marker.
	 *	@param marker the marker
	 *	@return the game label
	 */
	public static GameLabel createMarker(Marker marker) {
		GameLabel label = new GameLabel(createIcon(marker));
		label.setBackground(getColor(marker.getColor()));
		label.setBorder(BorderFactory.createRaisedBevelBorder());
		return label;
	}

	/**
	 *	Returns a draggable label for the given object belonging to the given player.
	 *	@param object the object
	 *	@param player the player
	 *	@param args specifying arguments
	 *	@return the label
	 */
	public static DraggableLabel createLabel(final Object object, Player player, Object... args) {
		ImageIcon icon = (args.length > 0 ? createIcon(object, args[0]) : createIcon(object));
		final DraggableLabel label = new DraggableLabel(icon, Antiquity.getAlpha(object instanceof Building ? "Default" : "Full"));
		label.putClientProperty("represents", object);

		/* Building */
		if(object instanceof Building) {
			label.setToolTipText(getDescription(object));
			label.setLocation(getLocation(object));
			label.setPolygon(getOutline(object));
			label.setBorder(new PolygonBevelBorder(PolygonBevelBorder.RAISED));
			label.setInheritsPopupMenu(false);
			if(object instanceof LandBuilding) {
				gui.menu.BuildingMenu menu = new gui.menu.BuildingMenu();
				menu.updateLandMenu(object instanceof Fisherman);
				((JMenuItem)menu.getComponent(0)).addActionListener(
					new ActionListener() {
						public void actionPerformed(ActionEvent e) {
							Point ref = getReferencePoint(object);
							ref.translate(label.getX(), label.getY());
							if(Antiquity.checkPlacement((LandBuilding)object, LandSystem.getPoint(ref))) label.setComponentPopupMenu(null);
						}
					}
				);
				label.setComponentPopupMenu(menu);
			}
			return label;
		}

		/* Resource */
		if(object instanceof Resource) {
			label.setToolTipText(getDescription(object, args.length > 0 ? args[0] : null));
			return label;
		}

		/* Worker */
		if(object instanceof Worker) {
			label.setBackground(getColor(((Worker)object).getColor()));
			label.setBorder(BorderFactory.createRaisedBevelBorder());
			label.setToolTipText(getDescription(object));
			label.setLocation(getLocation(object, args[0]));
			return label;
		}

		/* Unsupported game object */
		throw new IllegalArgumentException(object + " is not supported by GameUtilities.createLabel(Object, Object...)!");
	}

	/**
	 *	Creates an iconed label (used for pollution and grass markers).
	 *	@param object the game object
	 *	@param location the location
	 *	@return the label
	 */
	public static DraggableLabel createLabel(GameObject object, Point location) {
		ImageIcon icon = createIcon(object);
		int width = icon.getIconWidth();
		int height = icon.getIconHeight();
		DraggableLabel label = new DraggableLabel(icon);
		if(object instanceof ExplorationChit) {
			label.setOpaqueIcon(createIcon(object, ((ExplorationChit)object).getType()));
		} else if(object instanceof Saint) {
			label.setPolygon(getOutline(object));
			label.setBorder(new PolygonBevelBorder(PolygonBevelBorder.RAISED));
		}
		label.setToolTipText(getDescription(object));
		label.setLocation(location.x - width / 2, location.y - height / 2);
		label.setSize(width, height);
		label.setDraggable(false);
		return label;
	}

	/**
	 *	Returns the initial location of the given object.
	 *	@param object the object
	 *	@param args specifying arguments
	 *	@return the initial location
	 */
	public static Point getLocation(Object object, Object... args) {
		if(object instanceof Worker) {
			Point offset = (Point)Antiquity.getProperty("WorkerOffset");
			int step = (Integer)Antiquity.getProperty("WorkerStep");
			return new Point(offset.x + step * (Integer)args[0], offset.y);
		}

		if(object instanceof Famine) {
			Point offset1 = (Point)Antiquity.getProperty("FamineOffset");
			Point offset2 = (Point)Antiquity.getProperty("FamineTrackOffset");
			int value = ((Famine)object).getValue();
			if(value > 37) value = 37;
			int step = (Integer)Antiquity.getProperty("FamineTrackStep");
			int gap = (Integer)Antiquity.getProperty("FamineTrackGap");
			return new Point(offset1.x + offset2.x + value * step + (value < 19 ? 0 : gap), offset1.y + offset2.y);
		}

		if(object instanceof TurnOrder) {
			Point offset1 = (Point)Antiquity.getProperty("FamineOffset");
			Point offset2 = (Point)Antiquity.getProperty("TurnOrderOffset");
			int value = (Integer)args[0];
			int step = (Integer)Antiquity.getProperty("TurnOrderStep");
			return new Point(offset1.x + offset2.x + value * step, offset1.y + offset2.y);
		}

		if(object instanceof City) {
			Rectangle bounds = (Rectangle)Antiquity.getProperty("CityBounds");
			Point offset = (Point)Antiquity.getProperty("CityOffset");
			return new Point(bounds.x + offset.x, bounds.y + offset.y);
		}

		if(object instanceof Inn) {
			Point location = LandSystem.getPixel(((Building)object).getLocation());
			Point ref = getReferencePoint(object);
			location.translate(-ref.x, -ref.y);
			return location;
		}

		if(object instanceof Fisherman) {
			Rectangle bounds = (Rectangle)Antiquity.getProperty("FishermanBounds");
			Point offset = (Point)Antiquity.getProperty("FishermanOffset");
			return new Point(bounds.x + offset.x, bounds.y + offset.y);
		}

		if(object instanceof House) {
			Rectangle hsb = (Rectangle)Antiquity.getProperty("HouseScrollBounds");
			Point offset = (Point)Antiquity.getProperty("HouseOffset" + ((House)object).getIndex());
			return new Point(hsb.x + offset.x, hsb.y + offset.y);
		}

		if(object instanceof CityBuilding) {
			Rectangle bsb = (Rectangle)Antiquity.getProperty("BuildScrollBounds");
			Point offset = (Point)Antiquity.getProperty(object + "Offset");
			return new Point(bsb.x + offset.x, bsb.y + offset.y);
		}

		if(object instanceof Citymap) {
			return new Point((Point)Antiquity.getProperty("CityMapOffset" + args[0]));
		}

		if(object instanceof Saint) {
			Point offset1 = (Point)Antiquity.getProperty("FamineOffset");
			Point offset2 = (Point)Antiquity.getProperty("SaintsOffset");
			int value = (Integer)args[0];
			int step = (Integer)Antiquity.getProperty("SaintsStep");
			return new Point(offset1.x + offset2.x + value * step, offset1.y + offset2.y);
		}

		/* Unsupported game object */
		throw new IllegalArgumentException(object + " is not supported by GameUtilities.getLocation(Object, Object...)!");
	}

	/**
	 *	Returns the reference point for the given object.
	 *	@param object the object
	 *	@return the reference point
	 */
	public static Point getReferencePoint(Object object) {
		Polygon outline = getOutline(object);
		double dx, dy;

		/* Fisherman */
		if(object instanceof Fisherman) {
			int rotation = ((Rotatable)object).getRotation();
			int[] x = outline.xpoints;
			int[] y = outline.ypoints;
			dx = 0.25 * (2 * x[0] + x[1] + x[2]);
			dy = 0.25 * (2 * y[0] + y[1] + y[2]);

		/* Other objects */
		} else {
			Dimension size = outline.getBounds().getSize();
			dx = 0.5 * size.getWidth();
			dy = 0.5 * size.getHeight();
		}

		return new Point((int)Math.round(dx), (int)Math.round(dy));
	}

	/**
	 *	Returns the outline of the given object.
	 *	@param object the object
	 *	@return the outline
	 */
	public static Polygon getOutline(Object object) {
		if(object instanceof Fisherman) {
			switch(((Rotatable)object).getRotation()) {
				case 0: return new Polygon(new int[]{0, 35, 35, 0}, new int[]{0, 0, 70, 70}, 4);
				case 1: return new Polygon(new int[]{79, 62, 1, 18}, new int[]{36, 66, 31, 1}, 4);
				case 2: return new Polygon(new int[]{18, 1, 62, 79}, new int[]{66, 36, 1, 31}, 4);
			}
		}

		if(object instanceof Producer || object instanceof Inn || object instanceof Grave || object instanceof Saint) {
			ImageIcon icon = createIcon(object);
			int w = icon.getIconWidth();
			int h = icon.getIconHeight();
			return new Polygon(new int[]{0, w, w, 0}, new int[]{0, 0, h, h}, 4);
		}

		if(object instanceof CityBuilding) {
			Polygon poly = ((CityBuilding)object).getPolygon();
			Polygon ret = new Polygon();
			for(int i = 0; i < poly.npoints; i++) ret.addPoint(poly.xpoints[i] * CitySystem.UNIT_X, poly.ypoints[i] * CitySystem.UNIT_Y);
			return ret;
		}

		if(object instanceof City || object instanceof Hexfield || object instanceof Landtile) {
			Polygon poly = null;
			if(object instanceof City) {
				poly = new Polygon(
					new int[]{0, 1, 3, 4, 6, 7, 9, 10, 9, 10, 9, 7, 6, 4, 3, 1, 0, 1},
					new int[]{2, 1, 1, 0, 0, 1, 1, 2, 3, 4, 5, 5, 6, 6, 5, 5, 4, 3},
					18
				);
			} else if(object instanceof Hexfield) {
				poly = new Polygon(
					new int[]{-1, 1, 2, 1, -1, -2},
					new int[]{-1, -1, 0, 1, 1, 0},
					6
				);
			} else {
				poly = new Polygon(
					new int[]{0, 1, 3, 4, 6, 7, 9, 10, 12, 13, 15, 16, 18, 19, 21, 22, 24, 25, 27, 28, 27, 28, 27, 28, 27, 28, 27, 28, 27, 25, 24, 22, 21, 19, 18, 16, 15, 13, 12, 10, 9, 7, 6, 4, 3, 1, 0, 1, 0, 1, 0, 1, 0, 1},
					new int[]{5, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 17, 17, 16, 16, 15, 15, 14, 14, 13, 12, 11, 10, 9, 8, 7, 6},
					54
				);
			}
			Polygon ret = new Polygon();
			for(int i = 0; i < poly.npoints; i++) ret.addPoint(poly.xpoints[i] * LandSystem.UNIT_X, poly.ypoints[i] * LandSystem.UNIT_Y);
			return ret;
		}

		/* Unsupported game object */
		throw new IllegalArgumentException(object + " is not supported by GameUtilities.getOutline(Object)!");
	}

	/**
	 *	Returns the description of the given object.
	 *	@param object the object
	 *	@param args specifying arguments
	 *	@return the description
	 */
	public static String getDescription(Object object, Object... args) {
		if(object instanceof Grave) {
			String name = Antiquity.get("bld" + object);
			String info = ((args.length > 0 && args[0] != EmptySpace.getInstance()) ? String.format(" (%s)", Antiquity.get("bld" + args[0])) : "");
			return String.format(Antiquity.get("ftBuilding"), name, info, Antiquity.get("priceNone"), Antiquity.getFormatted("ttGrave"));
		}

		if(object instanceof Resource) {
			String type = "res" + (object instanceof Food ? "Food" : (object instanceof LuxuryGood ? "Lux" : "Mat"));
			String info = ((args.length > 0 && args[0] != null) ? "<br>(" + args[0] + ")" : "");
			return String.format(Antiquity.get("ftResource"), Antiquity.get("res" + object), Antiquity.get(type) + info);
		}

		if(object instanceof Building) {
			String info = "";
			if(object instanceof Limited && args.length > 0 && args[0] instanceof Boolean && (Boolean)args[0]) {
				int n = ((Limited)object).getTiles();
				info = String.format(" (%s)", n > 0 ? n : Antiquity.get("priceSold"));
			}
			String price = null;
			if(!(object instanceof Grave)) {
				if(object instanceof Farm) {
					price = Antiquity.get("priceSeed");
				} else {
					Building b = (Building)object;
					int wood = Antiquity.getPrice(b, Antiquity.WOOD);
					int stone = Antiquity.getPrice(b, Antiquity.STONE);
					int food = Antiquity.getPrice(b, Antiquity.FOOD);
					int lux = Antiquity.getPrice(b, Antiquity.LUX);
					price = "";
					if(wood > 0) price += getPrice(wood, "Wood");
					if(stone > 0) price += (price != "" ? ", " : "") + getPrice(stone, "Stone");
					if(food > 0) price += (price != "" ? ", " : "") + getPrice(food, "Food");
					if(lux > 0) price += (price != "" ? ", " : "") + getPrice(lux, "Lux");
					if(price == "") price = Antiquity.get("priceNone");
				}
			}
			String name = Antiquity.get("bld" + object);
			if(object instanceof House) name = String.format(name, ((House)object).getIndex());
			String description;
			if(object instanceof Producer && args.length > 0 && args[0] instanceof Resource) {
				description = String.format(Antiquity.getFormatted("ttProducer"), Antiquity.get("res" + args[0]));
			} else {
				description = Antiquity.getFormatted("tt" + object);
			}
			return String.format(Antiquity.get("ftBuilding"), name, info, price, description);
		}

		if(object instanceof Worker) {
			return Antiquity.get("ftWorker");
		}

		if(object instanceof Famine) {
			return String.format(Antiquity.get("ftFamine"), ((Famine)object).getValue());
		}

		if(object instanceof TurnOrder) {
			return String.format(Antiquity.get("ftTurnOrder"), args[0], args[1]);
		}

		if(object instanceof ExplorationChit) {
			return (args.length > 0 ? String.format(Antiquity.get("ftExploration"), Antiquity.get(args[0].toString())) : null);
		}

		if(object instanceof Pollution) {
			return (args.length > 0 ? String.format(Antiquity.get("ftPollution"), Antiquity.get(args[0].toString())) : null);
		}

		if(object instanceof Grass) {
			return null;
		}

		if(object instanceof Saint) {
			return String.format(
				Antiquity.get("ftSaint"),
				Antiquity.get("st" + object),
				Antiquity.get("priv" + object),
				Antiquity.get("tt" + object)
			);
		}

		/* Unsupported game object */
		throw new IllegalArgumentException(object + " is not supported by GameUtilities.getDescription(Object, Object...)!");
	}

	/**
	 *	Returns the formatted price of the given type.
	 *	@param n the price value
	 *	@param type the type
	 *	@return the formatted price
	 */
	public static String getPrice(int n, String type) {
		return String.format("%d %s", n, Antiquity.get("res" + type, n));
	}

	/**
	 *	Returns the color for a given <code>PlayerColor</code>.
	 *	@param color the <code>PlayerColor</code>
	 *	@return the color
	 */
	public static Color getColor(PlayerColor color) {
		switch(color) {
			case BLACK: return Antiquity.getColor("Black");
			case BLUE: return Antiquity.getColor("Blue");
			case RED: return Antiquity.getColor("Red");
			case YELLOW: return Antiquity.getColor("Yellow");
			default: return Antiquity.getColor("Famine");
		}
	}
}